[小ネタ] curlコマンドでクエリ文字列を指定してAPIを叩く時に試行錯誤したこと
CX事業本部Delivery部のアベシです。
筆者はAPIのテストにいつもPOSTMAN
やTHUNDER CLIENT
を使ってました。
そのため、いざcurlコマンドでAPIの検証してみたらいくつかハマったので、解消するまでの試行錯誤と結局どう書くのが良さそうと思ったのかを記事に残したいと思います。
エラー再現とレスポンス確認のため、以下のコードでfuga
とpiyo
の2つのクエリ文字列を取るAPIを作成しました。
このようなAPIにクエリ文字列を指定してGETメソッドでリクエストを送りパラメーターを渡す、というのがやりたかったことです。
CDKのコード
import { Stack, StackProps, aws_apigateway, aws_lambda_nodejs, } from 'aws-cdk-lib'; import { Runtime } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; export class ApiSampleStack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); // Lambdaのビルド const nameFunc = "Lambda_invoke_sample" const func = new aws_lambda_nodejs.NodejsFunction( this, nameFunc, { runtime: Runtime.NODEJS_18_X, functionName: nameFunc, entry: 'src/lambda/handlers/handler.ts', }, ); // API Gatewayリソースの作成 const nameRestApi ="API-sample"; const restApi = new aws_apigateway.RestApi(this, nameRestApi, { restApiName: nameRestApi, deployOptions: { stageName: 'v1', }, }); const resource= restApi.root.addResource('hoge'); resource.addMethod( 'GET', new aws_apigateway.LambdaIntegration(func), //クエリ文字列の指定 { requestParameters:{ "method.request.querystring.fuga": true, "method.request.querystring.piyo": true, } } ); } }
Lambda関数のコード
ハンドラーは受け取ったeventをクライアントに返します。 このeventの中に受信したクエリ文字列も入ってます。
interface Response { statusCode: number; body: string; } export const handler = async (event: Event): Promise<Response> => { return {statusCode: 200 , body:JSON.stringify(event, undefined, 2)}; };
-dオプションでクエリ文字列を指定しようと頑張る
curlには色々なオプションが用意されていて、-d
オプションを使って複数のクエリ文字列を送っている記事を目にしたのでこのオプションでやってみました
-dオプションのみ指定
以下のコマンドで叩きます
$ curl -i -d fuga=1 -d piyo=1 https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hoge
クエリ結果
HTTP/2 403 ~中略~ {"message":"Missing Authentication Token"}
"Missing Authentication Token"
が返って来ました。
このエラー、AWS公式のknowledge-center
の情報に以下の記述が有りました。
次の理由により、API Gateway REST API エンドポイントは「Missing Authentication Token」(認証トークンが見つかりません) エラーを返します。
API リクエストが、存在しないメソッドまたはリソースに対して行われた。
リクエスト先のURLとリソースは問題なさそうなのでメソッドに目をつけました。
curlコマンドではメソッドを指定しない場合はGETメソッドでリクエストするという記事を目にしたのでメソッド指定無しでリクエストしましたが、ここが怪しそうなので明示的にメソッドを指定して再チャレンジしました。
-Xオプションを追加
調べると-Xオプションで指定できそだったのでGETメソッドを指定してたたいてみました。
コマンド
$ curl -i -X GET -d fuga=1 -d piyo=1 https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hoge
結果
HTTP/2 403 server: CloudFront date: Thu, 09 Mar 2023 08:34:39 GMT content-type: text/html content-length: 915 x-cache: Error from cloudfront ~以下略~
結果はこれもうまく行かずでAPI GatewayのURLにリクエストしているのもかかわらず、CloudFrontからエラーが返ってきました。 この現象の発生理由はわかりませんでした。
-X を -Gオプションに変更
次に試したのが -G
というオプションです。
これはGETメソッドを強制するものとのことで試してみました。
コマンド
$ curl -i -G -d fuga=1 -d piyo=1 https:// **********.execute-api.ap-northeast-1.amazonaws.com/v1/hoge
結果
HTTP/2 200 ~中略~ "queryStringParameters": { "fuga": "1", "piyo": "1" }, ~以下略~ }
やっとリクエストが成功し、Lambda関数が受けたeventが返ってきました。
パラメーターもちゃんと受信できてました。
-dオプションはPOSTのリクエストボディにデータを含めるために使用するものだった
この-d
オプションはcontent-type
がapplication/x-www-form-urlencoded
のPOSTのリクエストボディのデータを指定するためのものでした 。
curlの公式に書かれてました。
今回の場合やりたいことはURLのパラメーターとしてクエリ文字列を指定したいので用途が間違ってました 。
helpの内容
-d, --data <data> HTTP POST data --data-ascii <data> HTTP POST ASCII data --data-binary <data> HTTP POST binary data --data-raw <data> HTTP POST data, '@' allowed --data-urlencode <data> HTTP POST data URL encode
curlコマンドでは以下記述で全てのhelp要素を表示できます。
$ curl --help all
こちらにいままで試したオプションについてしっかり説明されておりました。
最初からこちらを見てオプションを試していればよかったです。
なにか詰まったらまずはhelpや公式情報を見るのが一番手っ取り早いですね。
-dオプションを使わない方法
-dオプションを使わない方法を調べると以下の形で指定するのが一般的とのことでした。
curl url?<パラメーター名>=<値> //パラメーターが複数ある場合は&で続ける curl url?<パラメーター名>=<値>&?<パラメーター名>=<値>
ということで上記の指定のしかたでもう一度試してみました。
コマンド
curl -i https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hoge/?fuga=1&piyo=1
結果
zsh: no matches found: https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hoge/?fuga=1
今度はzshシェルがエラーを吐きました。
ファイルを実行しようとして見つからなかった?場合のようなエラーです。
Z shellがエラーを吐く原因
こちら調べると、Z Shell
特有のエラーらしくBashでは発生しないようです。
zshではコマンドに *,[],?
などのglobパターンマッチ記号が含まれているとファイルとして解析しようとするようで、今回URLのクエリ文字列の指定に記載した?
の所為でファイルと認識されたようです。
setopt nonomatch を .zshrc に追記
これを回避する方法としてsetopt nonomatch
を.zshrcに記述する方法が有りました。
.zshrcに記載してsource ~/.zshrcしてからもう一度先程のcurlコマンドでAPIコールしてみましょう。
結果
HTTP/2 200 ~中略~ "queryStringParameters": { "fuga": "1" },
今度は問題なく200とeventが返ってきました。
しかし、、、受け取ったパラメーターをよくよく見るとfugaしか返ってきません。
なんでや。。。
クエリ文字列を一つしか受信できないのはなぜ?
またまた調べると&
の後の記述がエンドポイントとして認識されておらずこのような現象が発生していることがわかりました。
curl公式には、エンドポイントのURLにシェルの制御に使う文字列( & ,? ,* などなど)を含む場合はシェルの干渉を防ぐためにURLはダブルクオートで囲みましょうと記載がありました。
ということでダブルクオートでURL全体を囲って叩いてみましょう
コマンド
curl -i "https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hoge/?fuga=1&piyo=1"
結果
HTTP/2 200 ~中略~ "queryStringParameters": { "fuga": "1", "piyo": "1" },
ついにいい感じにリクエスト成功しました。クエリ文字列もpiyoも含め全て返ってきました。
ダブルクオートよりもシングルクオートにすべき
このブログを公開してから先輩社員からダブルクオートでは変数展開の余地があり$
が含まれたURLにコールできなくなる可能性について教えてもらいました。
変数展開とは
例えば、以下のようなコマンドで$API_KEYや$QUERYのような環境変数を参照してしまいます。URLに$が含まれているとこのように変数を参照してしまい、意図せずにその値を含むURLとなってしまう、とい事が考えられます。
curl "https://example.com/api?key=$API_KEY&query=$QUERY"
しかしシングルクオートであればこの変数展開も発生することがありません。
よってダブルクオートは使用せずシングルにするというのが一番良いというのが結論となります。
結論
まずクエリ文字列の指定が必要なGETリクエストの場合、URLに続けてパラメーターを記載します。
パラメーターが複数ある場合にきちんと認識するためと、変数展開を防ぐためにURL全体をシングルクオート
で囲うのが一番シンプル且つ間違いがなさそうです!!
以上